package cn.pzaily.orange.manage.utils;

import cn.pzaily.orange.core.utils.ObjectUtils;
import cn.pzaily.orange.core.utils.StringUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Component
public class JwtTokenUtils {
    /**
     * 用户名称
     */
    private static final String CLAIM_KEY_USERNAME = "sub";
    /**
     * 创建时间
     */
    private static final String CLAIM_KEY_CREATE = "created";
    /**
     * 权限列表
     */
    private static final String CLAIM_KEY_AUTHORITIES = "authorities";

    /**
     * 密钥
     */
    @Value("${jwt.secret}")
    private String secret;

    /**
     * 有效时长
     */
    @Value("${jwt.expiration}")
    private Long expiration;

    @Value("${jwt.tokenHeader}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    /**
     * 根据负责生成JWT的token
     *
     * @param claims
     * @return
     */
    private String generateToken(Map<String, Object> claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从token中获取JWT中的负载
     *
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
        }
        return claims;
    }

    /**
     * 生成token过期时间
     *
     * @return
     */
    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * 从token中获取登录用户名
     *
     * @param token
     * @return
     */
    public String getUserNameFromToken(String token) {
        String username;
        try {
            Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 验证token是否有效
     *
     * @param token    客户端传入的token
     * @param userName 从数据库中查询出来的用户信息
     * @return
     */
    public boolean validateToken(String token, String userName) {
        String username = getUserNameFromToken(token);
        if (StringUtils.isEmpty(userName)) {
            return false;
        }
        return username.equals(userName) && !isTokenExpired(token);
    }

    /**
     * 判断token是否已经失效
     *
     * @param token
     * @return
     */
    private boolean isTokenExpired(String token) {
        Date expiredDate = getExpiredDateFromToken(token);
        return expiredDate.before(new Date());
    }

    /**
     * 从token中解析出过期时间
     *
     * @param token
     * @return
     */
    private Date getExpiredDateFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getExpiration();
    }

    /**
     * 生成token
     *
     * @param authentication
     * @return
     */
    public String generateToken(Authentication authentication) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, SecurityUtils.getUsername(authentication));
        claims.put(CLAIM_KEY_CREATE, new Date());
        claims.put(CLAIM_KEY_AUTHORITIES, authentication.getAuthorities());
        return this.tokenHead + generateToken(claims);
    }

    /**
     * 判断token是否可以被刷新
     *
     * @param token
     * @return
     */
    public boolean canRefresh(String token) {
        return !isTokenExpired(token);
    }

    /**
     * 刷新token
     *
     * @param token
     * @return
     */
    public String refreshToken(String token) {
        Claims claims = getClaimsFromToken(token);
        claims.put(CLAIM_KEY_CREATE, new Date());
        return generateToken(claims);
    }

    /**
     * 从header中获取token
     *
     * @param httpServletRequest
     * @return
     */
    public String getTokenFromHeader(HttpServletRequest httpServletRequest) {
        String header = httpServletRequest.getHeader(this.tokenHeader);
        if ((!StringUtils.isEmpty(header) && header.startsWith(this.tokenHead))) {
            return header.substring(this.tokenHead.length());
        }
        return null;
    }

    public Authentication getAuthenticationFromToken(HttpServletRequest request) {
        Authentication authentication = null;
        String token = this.getTokenFromHeader(request);
        if (StringUtils.isNotEmpty(token)) {
            if (ObjectUtils.isNull(SecurityUtils.getAuthentication())) {
                Claims claimsFromToken = this.getClaimsFromToken(token);
                if (ObjectUtils.isNull(claimsFromToken)) {
                    return null;
                }
                String username = claimsFromToken.getSubject();
                if (StringUtils.isEmpty(username)) {
                    return null;
                }
                if (this.isTokenExpired(token)) {
                    return null;
                }
                Object authors = claimsFromToken.get(CLAIM_KEY_AUTHORITIES);
                List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
                if (ObjectUtils.isNotNull(authentication) && authors instanceof List) {
                    for (Object object : (List) authors) {
                        authorities.add(new SimpleGrantedAuthority((String) ((Map) object).get("authority")));
                    }
                }
                authentication = new UsernamePasswordAuthenticationToken(username, token, authorities);
            } else {
                if (this.validateToken(token, SecurityUtils.getUsername())) {
                    authentication = SecurityUtils.getAuthentication();
                }
            }
        }
        return authentication;
    }
}
